Skip to content

Conversation

@joshheald
Copy link
Contributor

@joshheald joshheald commented Dec 9, 2022

Closes: #8085

Description

In sTAP Away, we're adding support for Apple's built in card readers to take in-person payments. We continue to use Stripe's SDK to handle payments.

During this work, we are looking to share code where practical with the bluetooth reader flows, and fix issues in those flows which were less obvious with an external reader. One such issue is cancelation from card readers, including Apple's built in card reader.

On the built-in reader modal which Apple provide, there is a x button to cancel the payment. On the WisePad 3 reader, there are customer-facing buttons, and tapping the red x button when collecting card details will cancel the payment.

In both these cases, we should end the payment flow. For the WisePad 3 reader, we should inform the merchant that the payment was canceled, so they understand how the external device is impacting the payment flow.

Testing instructions

Using an iPhone XS or newer on iOS 16.0 or newer on a US store

  1. Navigate to Menu > Settings > Experimental features
  2. Turn on Tap to Pay on iPhone
  3. Navigate to Menu > Payments > Collect payment
  4. Go through the payment flow, and select Card on the payment method screen
  5. When asked for a reader type, tap Tap to Pay on iPhone and go through the Terms of Service Apple ID linking (if you've not done so before)
  6. Tap the x on Apple's screen to cancel
  7. Observe that the modal is dismissed, and the payment flow behind it is also dismissed
  8. Observe that the card_present_collect_payment_canceled tracks event is logged as expected

On a CA store using a physical WisePad 3 reader – new flow

  1. Navigate to Menu > Settings > Experimental features
  2. Turn on Tap to Pay on iPhone
  3. Navigate to Menu > Payments > Collect payment
  4. Go through the payment flow, and select Card on the payment method screen
  5. Wait for the reader to connect and ask for the card to be tapped or inserted
  6. Press the red x button on the reader
  7. Observe that the payment canceled error (see below) is shown
  8. Observe that the card_present_collect_payment_canceled tracks event is logged as expected

On a CA store using a physical WisePad 3 reader – existing flow

  1. Navigate to Menu > Settings > Experimental features
  2. Turn off Tap to Pay on iPhone
  3. Navigate to Menu > Payments > Collect payment
  4. Go through the payment flow, and select Card on the payment method screen
  5. Wait for the reader to connect and ask for the card to be tapped or inserted
  6. Press the red x button on the reader
  7. Observe that the payment flow is dismissed
  8. Observe that the card_present_collect_payment_canceled tracks event is logged as expected

Screenshots

WisePad 3 reader screen

payment canceled on reader

Apple built in reader flow

cancel-payment-on-built-in-reader.mp4

  • I have considered if this change warrants user-facing release notes and have added them to RELEASE-NOTES.txt if necessary.

Previously (prior to  5233d70 ) we didn’t dismiss any alerts when cancel was tapped on the reader (or built in reader modal) because we ignored the error.

This corrects the comments, warnings, and errors produced (in the StripeCardReaderService) now that we don’t ignore the error any more.

It also stops showing an alert (with a try again button) when the error is recieved in the CollectOrderPaymentUseCase, and instead dismisses the payment alerts.

Now, in the new flow, we handle cancelations from card readers and from buttons in the app equally: we dismiss the alerts and track the cancelation events.
If the user cancels the payment on the reader, it can help the merchant to specifically notify them that the payment has not been taken, and they still need to capture payment.

We do not do this with the built-in reader because it’s clear when everything happens on the same device.
@joshheald joshheald added type: enhancement A request for an enhancement. feature: mobile payments Related to mobile payments / card present payments / Woo Payments. labels Dec 9, 2022
@joshheald joshheald added this to the 11.7 milestone Dec 9, 2022
@joshheald joshheald requested a review from toupper December 9, 2022 14:00
@wpmobilebot
Copy link
Collaborator

wpmobilebot commented Dec 9, 2022

You can test the changes from this Pull Request by:
  • Clicking here or scanning the QR code below to access App Center
  • Then installing the build number pr8369-fc1213a on your iPhone

If you need access to App Center, please ask a maintainer to add you.

@joshheald
Copy link
Contributor Author

@jostnes @lmischner: optional review but since you both noticed this, I thought it's worth tagging you here :)

@toupper: no rush, I've set it for 11.7

@joshheald joshheald marked this pull request as ready for review December 9, 2022 14:13
Base automatically changed from issue/8289-show-follow-instructions-during-built-in-collect-payment to trunk December 9, 2022 17:26
@toupper toupper self-assigned this Dec 12, 2022
@toupper
Copy link
Contributor

toupper commented Dec 12, 2022

@joshheald thanks for the PR! it tests well and the code looks good to me, I just added a couple of suggestions.

promise(.failure(CardReaderServiceError.paymentCancellation(underlyingError: underlyingError)))
DDLogWarn("💳 Warning: collect payment cancelled \(error)")
/// If we've not used the cancellable in the app, the cancellation must have come from the reader
if self?.paymentCancellable != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a blocker but, is there another way to ascertain that the cancellation came from the reader? Tying it to whether the paymentCancellable is nil or not seems frail to me, we could change our implementation (e.g making paymentCancellable nil in another way) and it would break this logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, but there's no way that Stripe mention or that I was able to see. I'll check again but this has implications for the other comment on this PR (if there's no way to find out from Stripe, we won't be confident that it was cancelled on device when we get the error from Stripe)


/// A command was cancelled on the reader.
/// Note that this is not produced by Stripe, we have to infer it from commandCancelled.
case commandCancelledOnReader
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we already have a case commandCancelled, I would collapse these two checks, or at least add further information to the other case. Otherwise, it might be confusing: If commandCancelledOnReader is true, commandCancelled would be false, which is not a valid statement; the command was indeed canceled.

case commandCancelledOnDevice
case commandCancelledOnReader

or

enum CommandCancellationContext {
    case device
    case reader
}
...
case commandCancelled(context: CommandCancellationContext)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, but I think that doing this depends on Stripe telling us the context when we get the error, as we need to convert from the Stripe commandCancelled before we have the detail of what triggered the cancellation.

While we could take the second suggestion and default to device, converting (as we do now) to reader when we have the context. There's no difference in code with these, but by saying device for reader cancellations too early, we're expressing confidence about something which we don't know for sure (commandCancelled could be either source)

@joshheald
Copy link
Contributor Author

joshheald commented Dec 12, 2022

thanks for the PR! it tests well and the code looks good to me, I just added a couple of suggestions.

Thanks for the review @toupper 😊

Unfortunately Stripe don't tell us what the cancellation source is, so we have to handle the unknown case. In bc89e2b I've added a cancellation source enum, which lets us sometimes update the cancellation to have the known type.

In bee90e9 I've updated the service to add a flag which we set when a cancellation starts from the app. Then, if it's not set when we get a cancellation from Stripe during the collect step, we assume that it came from the reader.

@joshheald joshheald enabled auto-merge December 12, 2022 13:48
@joshheald joshheald merged commit 618b3a0 into trunk Dec 12, 2022
@joshheald joshheald deleted the issue/8085-cancellation-from-card-readers branch December 12, 2022 18:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature: mobile payments Related to mobile payments / card present payments / Woo Payments. type: enhancement A request for an enhancement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Mobile Payments] Handle cancellation from card readers

4 participants